package com.ayf.payment.game.api.task.settle;

import cn.hutool.core.collection.CollUtil;
import com.ayf.payment.game.api.dto.criteria.OrderInfoCriteria;
import com.ayf.payment.game.api.dto.platform.MerchantSettleDTO;
import com.ayf.payment.game.api.service.MerchantPayOrderService;
import com.ayf.payment.game.api.service.MerchantService;
import com.ayf.payment.game.api.service.platform.MerchantCashOutService;
import com.ayf.payment.game.api.service.platform.MerchantSettleService;
import com.ayf.payment.game.api.utils.DateUtils;
import com.ayf.payment.game.api.utils.SnowFlakeUtils;
import com.swwx.charm.commons.lang.utils.LogPortal;
import com.swwx.charm.zookeeper.exception.GetLockFailedException;
import com.swwx.charm.zookeeper.exception.ReleaseLockFailedException;
import com.swwx.charm.zookeeper.lock.DistributedLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * @author ad
 * @Title: OrderSettleTaskBasic
 * @Copyright: Copyright (c) 2020
 * @Description: <br>
 * @Company: a.com
 * @Created on 2020/10/2217:33
 */
@Component
public abstract class OrderSettleTaskBasic {

    @Autowired
    private MerchantService merchantService;
    @Autowired
    private MerchantSettleService merchantSettleService;
    @Autowired
    private MerchantCashOutService merchantCashOutService;
    @Autowired
    private MerchantPayOrderService merchantPayOrderService;

    /**
     * @param distributedLock
     */
    protected void run(DistributedLock distributedLock) {
        boolean lock = false;
        String traceId = SnowFlakeUtils.getTraceId();
        String lockName = this.getClass().getSimpleName().concat(System.getenv("XY_GAME_PAY_LOCK_FLAG"));
        try {
            if (lock = distributedLock.getLock(lockName)) {
                LogPortal.info("[{}] get lock and run [{}] ", traceId, lockName);
                Date dayBeforeDay = DateUtils.getDayBeforeDay(1);

                Integer date = DateUtils.dateToInteger(dayBeforeDay);
                List<MerchantSettleDTO> settleList = merchantService.getSettleList(date, null);
                if (CollUtil.isEmpty(settleList)) {
                    return;
                }
                OrderInfoCriteria orderCriteria = new OrderInfoCriteria();
                String yesterday = DateUtils.dateToString(dayBeforeDay, DateUtils.formatPattern);
                orderCriteria.setStartTime(yesterday + " 00:00:00");
                orderCriteria.setEndTime(yesterday + " 23:59:59");
                orderCriteria.setDate(date);
                //提现
                Map<Integer, Map<String, Object>> withdrawalMap = merchantCashOutService.selectSumWithdrawal(orderCriteria);
                //代付
                Map<Integer, Map<String, Object>> payMap = merchantPayOrderService.selectSumPay(orderCriteria);

                int num = settleList.size();
                LogPortal.info("[{}][{}]每日结算开始执行，执行的总次数为[{}]", traceId, lockName, num);
                Integer settleDate = DateUtils.dateToInteger(DateUtils.getNowDate());
                String settleOrdeCreateStr = "S" + DateUtils.dateToString(DateUtils.getDayBeforeDay(1), "yyMMdd");
                //初始化信号
                final CountDownLatch countDownLatch = new CountDownLatch(num);
                for (int i = 0; i < num; i++) {
                    int count = i + 1;
                    MerchantSettleDTO dto = settleList.get(i);
                    LogPortal.info("[{}]开始执行第[{}]次结算,商户id[{}],结算信息为[{}]", traceId, i + 1, dto.getMerchantId(), dto);
                    submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                String settleOrderNo = settleOrdeCreateStr + SnowFlakeUtils.getUniqueId();
                                dto.setSettleDate(settleDate);
                                dto.setSettleOrderNo(settleOrderNo);
                                merchantSettleService.daySettle(dto, withdrawalMap, payMap);
                                LogPortal.info("[{}]第[{}]次结算成功,商户id[{}]", traceId, count, dto.getMerchantId());
                            } catch (Exception e) {
                                LogPortal.error("[{}]第[{}]次结算失败,商户id[{}],发生了[{}]异常，错误描述[{}]，本次处理的商户信息为[{}]",e,traceId,
                                        count, dto.getMerchantId(), e.getClass().getName(), e.getMessage(), dto);
                            } finally {
                                //线程执行完成之后标记信号表示完成
                                countDownLatch.countDown();
                            }
                        }
                    });
                }
                LogPortal.info("[{}]每日结算任务执行完成，本次处理的商户总数量为[{}]", traceId, settleList.size());
                //等待线程池任务执行完毕
                countDownLatch.await();
            } else {
                LogPortal.info("[{}][{}]do not get lock ", traceId, lockName);
            }
        } catch (GetLockFailedException e) {
            LogPortal.error("{} >>> [DISTRIBUTED_LOCK_ACQUIRE_FAILED_ZK] 分布式锁获取失败,锁名称为:{}", traceId, lockName);
            LogPortal.error("{} >>> {} 获取lock失败", e, traceId, lockName);
        } catch (Exception e) {
            LogPortal.error("{} >>> {} 任务执行出错 ", e, traceId, lockName);
        } finally {
            if (lock) {
                LogPortal.info(traceId + " >>> " + lockName + " finished.");
                try {
                    distributedLock.releaseLock(lockName);
                } catch (ReleaseLockFailedException e) {
                    LogPortal.error("{} >>> [DISTRIBUTED_LOCK_RELEASE_FAILED_ZK] 分布式锁释放失败,锁名称为:{}", traceId, lockName);
                    LogPortal.error("{} >>> {} 释放失败", e, traceId, lockName);
                }
            }
        }
    }

    protected abstract void submit(Runnable task);
}
